Lecture 7¶
September 23, 2024
While loops¶
While loops execute as long as a statement remains true. Truth values are checked before running the code block and between runs of the code block.
a = 0
b = 7
while a < b:
print(f'a = {a}')
a += 2
a
a = 0 a = 2 a = 4 a = 6
8
a = 9
b = 7
while a < b:
print(f'a = {a}')
a += 2
a
9
Example: A triangular number has the form $1+2+\ldots+n$ for some $n \geq 1$.¶
Write a function that returns the list of all the triangular numbers less than $k$.
def triangular(k):
n = 1
tri = 1
tris = []
while tri < k:
tris.append(tri)
n += 1
tri += n
return tris
triangular(100)
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91]
triangular(105)
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91]
triangular(106)
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105]
While with break¶
Sometimes it is convienient to exit the loop from somewhere in the middle of a loop statement. You can break out at any point with the break
statement:
i = 1
while True:
i = 2
break
i
2
Example a drunk gambler¶
Write a function that prints out the behavior of a drunk gambler who starts with n
dollars. Each round, he
- Buys a $\$1$ drink with 25% probability.
- He makes a bet. If he wins, he wins $\$1$. If he loses, he loses $\$1$. His chances of winning are 50%.
The loop should stop if he has no money left or as soon has he has 8 drinks. Return the amount of money he has left.
We will use random()
which produces a uniformly random float in $[0, 1]$.
random()
0.3924829837385204
def drunk_gambler(n):
drinks = 0
while True:
# bet
if random() < 1/4:
n -= 1
drinks += 1
print(f'Gambler drinks: This is drink #{drinks}$, he has ${n}.')
if drinks >= 8:
print(f'Gambler has had too many drinks: Thrown out with ${n}!')
break
if n <= 0:
print(f'Gambler drank away his last dollar!')
break
if random() > 1/2:
# wins
n += 1
print(f'Gambler wins: He has ${n}')
else:
# loses
n -= 1
print(f'Gambler loses: He has ${n}')
if n <= 0:
print(f'Gambler is now broke and goes home!')
break
drunk_gambler(3)
Gambler wins: He has $4 Gambler drinks: This is drink #1$, he has $3. Gambler wins: He has $4 Gambler drinks: This is drink #2$, he has $3. Gambler loses: He has $2 Gambler loses: He has $1 Gambler wins: He has $2 Gambler loses: He has $1 Gambler wins: He has $2 Gambler drinks: This is drink #3$, he has $1. Gambler loses: He has $0 Gambler is now broke and goes home!
drunk_gambler(10)
Gambler drinks: This is drink #1$, he has $9. Gambler wins: He has $10 Gambler wins: He has $11 Gambler wins: He has $12 Gambler drinks: This is drink #2$, he has $11. Gambler wins: He has $12 Gambler drinks: This is drink #3$, he has $11. Gambler wins: He has $12 Gambler wins: He has $13 Gambler drinks: This is drink #4$, he has $12. Gambler loses: He has $11 Gambler wins: He has $12 Gambler drinks: This is drink #5$, he has $11. Gambler wins: He has $12 Gambler drinks: This is drink #6$, he has $11. Gambler wins: He has $12 Gambler drinks: This is drink #7$, he has $11. Gambler loses: He has $10 Gambler wins: He has $11 Gambler wins: He has $12 Gambler loses: He has $11 Gambler loses: He has $10 Gambler drinks: This is drink #8$, he has $9. Gambler has had too many drinks: Thrown out with $9!
Scopes¶
References:
Python associates variable names with objects. The scope of a variable is a code block in which the variable is available.
Local Scope:¶
A function defined within a funtion is only available within that function.
def shift(x):
k = 3
return x+k
k
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[12], line 4 2 k = Integer(3) 3 return x+k ----> 4 k NameError: name 'k' is not defined
shift(1)
4
Enclosing (or nonlocal) scope¶
For nested functions, the variables in the outer function are available to the inner function.
def shift_square(x):
k = 3
def square():
# The variable k is accessible here.
return k^2
return x+square()
shift_square(10)
19
Global scope¶
This is the scope of variables created in a Jupyter notebook. (If you do more porgramming, it will be the variables defined in your program, script, or module).
kk = 4
def shift_square2(x):
k = 3
def square():
# The variable kk is accessible from global scope
return kk^2
return x+square()
shift_square2(0)
16
Built-in scope¶
These are the objects made available by Python/Sage. For example, the function var
is in global scope.
Where does a variable come from?¶
When a variable is accessed, Python and Sage use the LEGB
rule:
- First it checks if the variable is in the local scope.
- If it finds nothing, it checks the enclosing (nonlocal) scope.
- If it finds nothing, it checks the global scope.
- If it finds nothing, it checks the built-in scope.
- If it finds nothing, it produces a
NameError
.
What about when you assign a variable?¶
Assignments are made to the local scope, unless you specify otherwise. Example:
j = 3
def change_j():
j = 4
change_j()
j
3
j = 3
def change_j():
global j # Tell it to use j from the global scope
j = 4
change_j()
j
4
j = 0
def outer():
j = 4
def inner():
global j
j = j+1
print(f'in inner, j = {j}')
inner()
print(f'in outer, j = {j}')
outer()
print(f'in global, j = {j}')
in inner, j = 1 in outer, j = 4 in global, j = 1
j = 0
def outer():
j = 4
def inner():
nonlocal j
j = j+1
print(f'in inner, j = {j}')
inner()
print(f'in outer, j = {j}')
outer()
print(f'in global, j = {j}')
in inner, j = 5 in outer, j = 5 in global, j = 0
j = 0
def outer():
j = 4
def inner():
j = j+1
print(f'in inner, j = {j}')
inner()
print(f'in outer, j = {j}')
outer()
print(f'in global, j = {j}')
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) Cell In[22], line 9 7 inner() 8 print(f'in outer, j = {j}') ----> 9 outer() 10 print(f'in global, j = {j}') Cell In[22], line 7, in outer() 5 j = j+Integer(1) 6 print(f'in inner, j = {j}') ----> 7 inner() 8 print(f'in outer, j = {j}') Cell In[22], line 5, in outer.<locals>.inner() 4 def inner(): ----> 5 j = j+Integer(1) 6 print(f'in inner, j = {j}') UnboundLocalError: cannot access local variable 'j' where it is not associated with a value
An UnboundLocalError
occurs if you read from a variable outside the local scope, and then later write to it (without declaring the variable global
or nonlocal
). So, this is okay:
j = 0
def outer():
j = 4
def inner():
j = 10
print(f'in inner, j = {j}')
inner()
print(f'in outer, j = {j}')
outer()
print(f'in global, j = {j}')
in inner, j = 10 in outer, j = 4 in global, j = 0
Remarks on functions returning functions¶
It is common to ask for me to ask you to write a function that returns a function. Most commonly, you want to think of the outer function as defining constants for the inner function to use; the inner function should typically not modify those values. For example:
def shift(n):
def shift_by_n(x):
return x+n
return shift_by_n
f = shift(4)
f(10)
14
For a case that you might want to modify values, suppose we want to keep track of the number of shifts applied.
def shift_with_counter(n):
applications = 0
def shift_by_n(x):
nonlocal applications
applications += 1
return x+n
def counter():
return applications
return shift_by_n, counter
f, c = shift_with_counter(10)
f(4)
14
f(5)
15
c()
2
l = [f(i) for i in range(20)]
c()
22
Vectors and VectorSpaces¶
Sage math has built in support for vectors. For example:
v = vector([1, 4, -4])
Elements can be accessed just like lists and tuples:
v[0]
1
v[0].parent()
Integer Ring
Sage finds a common parent for all enties. Conversions are handled automatically. This parent can be accessed with the base_ring()
method:
v.base_ring()
Integer Ring
w = vector([1, 4/3, -5])
w.base_ring()
Rational Field
w[0].parent()
Rational Field
You can explicitly choose a base ring by passing it to the vector constructor:
v = vector(AA, [1, 2])
v
(1, 2)
Changing entries¶
Like a list, you can (by default) change the entries in a vector.
v
(1, 2)
v[0] = 3
v
(3, 2)
But, you can set a vector to be immutable. Then it can no longer be changed.
v.set_immutable()
v[0] = 9
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[41], line 2 1 v.set_immutable() ----> 2 v[Integer(0)] = Integer(9) File ~/Software/Sage/sage-10.3/src/sage/modules/free_module_element.pyx:1885, in sage.modules.free_module_element.FreeModuleElement.__setitem__() 1883 """ 1884 if self._is_immutable: -> 1885 raise ValueError("vector is immutable; please change a copy instead (use copy())") 1886 cdef Py_ssize_t d = self._degree 1887 cdef Py_ssize_t start, stop, step, slicelength ValueError: vector is immutable; please change a copy instead (use copy())
Immutable vectors are useful: You can use them as keys in dictionaries.
Vector spaces¶
Assuming that a vector is defined over a field, the parent of a vector is a vector space.
v = vector([1, 1/3])
V = v.parent()
V
Vector space of dimension 2 over Rational Field
You can also define a vector space explicitly:
V = VectorSpace(RR, 3)
V
Vector space of dimension 3 over Real Field with 53 bits of precision
Then you can define objects in the vector space by passing parameters, which are automatically converted to the Field.
v = V([3, pi, 1/3])
v
(3.00000000000000, 3.14159265358979, 0.333333333333333)
V.zero()
(0.000000000000000, 0.000000000000000, 0.000000000000000)
The parent of the entries should always be at least a ring. If a ring is used, you get a FreeModule
instead.
v = vector([1, 4])
V = v.parent()
V
Ambient free module of rank 2 over the principal ideal domain Integer Ring
FM = FreeModule(ZZ, 2)
FM
Ambient free module of rank 2 over the principal ideal domain Integer Ring
Free modules can be used in much the same way as vector spaces.
FM([2^9, 4])
(512, 4)
Vector operations:¶
v = vector([1/3, 3, 5])
w = vector(RR, [pi, sqrt(2), 1])
v, w
((1/3, 3, 5), (3.14159265358979, 1.41421356237310, 1.00000000000000))
v+w
(3.47492598692313, 4.41421356237309, 6.00000000000000)
v-w
(-2.80825932025646, 1.58578643762690, 4.00000000000000)
Dot product:
v*w
10.2898382383159
Cross product:
v.cross_product(w)
(-4.07106781186548, 15.3746299346156, -8.95337343997835)
Scalar multiplication:
sqrt(2)*w
(3.14159265358979*sqrt(2), 1.41421356237310*sqrt(2), 1.00000000000000*sqrt(2))
Classes¶
I'll follow the Official Python Tutorial on classes
Classes are a way of bundling data with functionality.
Class attributes¶
Here is a simple class whose name is SomeClassName
. It has one class attribute, counter
.
class SomeClassName:
counter = 0
SomeClassName.counter
0
You can change the value if you wish:
SomeClassName.counter = 3
SomeClassName.counter
3
An attribute can also be a function. But I'll postpone discussing this.
Instantiation¶
The main idea of a class is to be able to create many instances of similar objects, that would typically be associated with different data. You can then interact with thes objects in a similar way. For example:
class ProjectivePoint:
V = VectorSpace(QQ, 3)
def __init__(self, v):
self.v = ProjectivePoint.V(v)
pt1 = ProjectivePoint([1, 2, 3])
pt1
<__main__.ProjectivePoint object at 0x7fced83914d0>
pt1.v
(1, 2, 3)
pt1.v.parent()
Vector space of dimension 3 over Rational Field
pt2 = ProjectivePoint([0,0,1/2])
pt2.v
(0, 0, 1/2)
Instance Methods¶
class ProjectivePoint:
V = VectorSpace(QQ, 3)
def __init__(self, v):
self.v = ProjectivePoint.V(v)
assert self.v != ProjectivePoint.V.zero()
def is_infinite(self):
return self.v[2] == 0
def x(self):
if self.v[2] == 0:
return Infinity
return self.v[0] / self.v[2]
def y(self):
if self.v[2] == 0:
return Infinity
return self.v[1] / self.v[2]
pt1 = ProjectivePoint([1, 2, 3])
pt1
<__main__.ProjectivePoint object at 0x7fced6859bd0>
pt1.x()
1/3
pt1.y()
2/3
pt2 = ProjectivePoint([1, 2, 0])
pt2
<__main__.ProjectivePoint object at 0x7fced635f590>
pt2.is_infinite()
True
pt2.x()
+Infinity
pt2.y()
+Infinity
pt3 = ProjectivePoint([0,0,0])
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) Cell In[123], line 1 ----> 1 pt3 = ProjectivePoint([Integer(0),Integer(0),Integer(0)]) Cell In[115], line 6, in ProjectivePoint.__init__(self, v) 4 def __init__(self, v): 5 self.v = ProjectivePoint.V(v) ----> 6 assert self.v != ProjectivePoint.V.zero() AssertionError: